package com.declum.archon.chunkserver.handler;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

import org.apache.log4j.Logger;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.TransportType;
import org.apache.mina.transport.socket.nio.SocketSessionConfig;

import com.declum.archon.chunkserver.handler.request.IRequest;
import com.declum.archon.chunkserver.handler.request.RequestHandlerFactory;
import com.declum.archon.chunkserver.server.IChunkServer;

/**
 * @author Clement
 * 
 */
public class ChunkServerHandler extends IoHandlerAdapter {
	CharsetDecoder			decoder;
	CharsetEncoder			encoder;
	protected Logger		logger;
	IChunkServer			server;
	RequestHandlerFactory	requestFactory;

	/**
	 * 
	 */
	public ChunkServerHandler() {
		this.logger = Logger.getLogger(ChunkServerHandler.class);
		this.decoder = Charset.defaultCharset().newDecoder();
		this.encoder = Charset.defaultCharset().newEncoder();
		this.requestFactory = RequestHandlerFactory.getInstance();
	}

	/**
	 * This method be called if any exception occurred in handing of session.
	 * Can be used in closing of some resources
	 */
	@Override
	public void exceptionCaught(IoSession session, Throwable t)
			throws Exception {
		t.printStackTrace();
		this.logger.error(t.getMessage(), t.getCause());

		session.close();
	}

	/**
	 * Whenever data is received form client, this method will be called.<br />
	 * <h1>Algorithm</h1>
	 * <ul>
	 * <li>Get the stored buffer from the session. This is used to store
	 * unformed command from the client if client sends data in byte by byte
	 * manner. So until we reach the required length of the command thats 10, we
	 * will be accumulating the bytes in stored buffer in session.
	 * <li>Once the command is received, we will be getting the required handler
	 * from {@link RequestHandlerFactory}
	 * <li>if we already got the request handler in hand, then every byte will
	 * be sends to the request hander, and we do nothing to store or receive.
	 * 
	 * </ul>
	 */
	@Override
	public void messageReceived(IoSession session, Object msg) throws Exception {
		ByteBuffer in = (ByteBuffer) msg;
		/**
		 * Get the request handler
		 */
		Object requestObject = session.getAttribute("request");
		IRequest request = null;
		if (requestObject == null) {
			/**
			 * Get the stored buffer
			 */
			ByteBuffer storedBuffer = (ByteBuffer) session
					.getAttribute("storedBuffer");
			/**
			 * Move the cursor at end of the buffer
			 */
			storedBuffer.position(storedBuffer.remaining());
			/**
			 * Append the client send data at the end of the stored buffer
			 */
			storedBuffer.put(in);
			/**
			 * Flip the buffer
			 */
			storedBuffer.flip();
			if (storedBuffer.remaining() >= 10) {
				/**
				 * Get the Request handler for this command
				 */
				request = this.requestFactory.getRequestInstance(session,
						storedBuffer);
				/**
				 * Set the server as this
				 */
				request.setServerInstance(this.server);

				/**
				 * Store the handler to the session for future uses
				 */
				session.setAttribute("request", request);
			} else {
				this.logger
						.debug("Information is not enough to form the command");
				return;
			}
		} else {
			this.logger.debug("Request handler is taken from session");
			request = (IRequest) requestObject;
		}
		this.logger.debug("Processing request is started");
		request.process(session, msg);
	}

	/**
	 * Sets the session parameters for session. It also creates a buffer which
	 * can be used across the method calls
	 * 
	 * @throws Exception
	 *             if any exception occurred during the creation of session
	 */
	@Override
	public void sessionCreated(IoSession session) throws Exception {
		this.logger.debug("Session created");

		if (session.getTransportType() == TransportType.SOCKET) {
			((SocketSessionConfig) session.getConfig())
					.setReceiveBufferSize(2048);
		}

		/**
		 * Set session time out
		 */
		session.setIdleTime(IdleStatus.BOTH_IDLE, 100);
		/**
		 * A buffer which can be used while handing the response.
		 */
		ByteBuffer storedBuffer = ByteBuffer.allocate(1024);
		storedBuffer.setAutoExpand(true);
		storedBuffer.flip();
		/**
		 * The the created buffer to session
		 */
		session.setAttribute("storedBuffer", storedBuffer);
	}

	/**
	 * This method set the ChunkServer instance for the request handler. It also
	 * initializes the RequestFactor with the server instance
	 * 
	 * @param chunkServer
	 *            chunk server instance
	 * 
	 * @throws NullPointerException
	 *             if the argument is null
	 */
	public void setChunkServer(IChunkServer chunkServer) {
		if (chunkServer != null) {
			this.server = chunkServer;
			RequestHandlerFactory.initialize(this.server);
		} else {
			throw new NullPointerException("IChunkServer cannot be null");
		}
	}
}
